﻿using StudentsPrenseLog.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StudentsPrenseLog.Repositories
{
    public class StudentRepository
    {
        private class RecordLocation
        {
            public static List<RecordLocation> FromPositions(List<long> positions)
            {
                List<RecordLocation> locations = new List<RecordLocation>();
                long lastPosition = 2;

                if (positions != null && positions.Count > 0)
                {
                    foreach (long pos in positions)
                    {
                        locations.Add(new RecordLocation() { StartPos = lastPosition, EndPos = pos - 1 });
                        lastPosition = pos + 3;
                    }
                }
                return locations;
            }

            public long StartPos { get; set; }
            public long EndPos   { get; set; }

            public long Length
            {
                get { return this.EndPos - this.StartPos; }
            }
        }

        private readonly String  _filePath;
        private readonly String _dateFormat;
        private readonly String _fileDelimeter;

        private readonly Encoding _defaultEncoding = Encoding.Unicode;

        private FileStream       _fileStream;

        private bool IsFileOpened
        {
            get { return _fileStream != null; }
        }

        public StudentRepository(string filePath,string dateFormat,string fileDelimeter)
        {
            if (String.IsNullOrWhiteSpace(filePath))
                throw new ArgumentNullException("filePath");

            if (String.IsNullOrWhiteSpace(dateFormat))
                throw new ArgumentNullException("dateFormat");

            if (String.IsNullOrWhiteSpace(fileDelimeter))
                throw new ArgumentNullException("fileDelimeter");

            _filePath      = filePath;
            _dateFormat    = dateFormat;

            char ch = System.Convert.ToChar(System.Convert.ToUInt32(fileDelimeter));

            _fileDelimeter = new String(ch,1);

        }

        private void CheckFileStatus()
        {
            if (!this.IsFileOpened)
            {
                _fileStream = File.Open(_filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
            }
        }

        private RecordLocation GetRecordLocationByKey(Guid key)
        {
            List<RecordLocation> allLocation = GetRecordsPosition();

            foreach (RecordLocation rl in allLocation)
            {
                _fileStream.Seek(rl.StartPos, SeekOrigin.Begin);

                byte[] guidEncoded = new byte[72];
                _fileStream.Read(guidEncoded, 0, guidEncoded.Length);

                String keyStr = Encoding.Unicode.GetString(guidEncoded).ToUpper();
                Guid parsedKey;

                if (Guid.TryParse(keyStr, out parsedKey))
                {
                    if (key.Equals(parsedKey))
                        return rl;
                }
            }
            return null;
        }

        private void SeekToStart()
        {
            _fileStream.Seek(2, SeekOrigin.Begin);
        }

        private List<RecordLocation> GetRecordsPosition()
        {
            CheckFileStatus();

            SeekToStart();

            int readByte;
            char firstEol  = '\r';
            char secondEol = '\n';

            byte firstByte  = 0;
            byte secondByte = 0;
            bool firstRead = false;
            bool expectNextIsEol = false;

            long counter      = 0;
            List<long> result = new List<long>();

            while ((readByte = _fileStream.ReadByte()) != -1)
            {
                if (firstRead)
                {
                    secondByte = (byte)readByte;

                    char readChar = BitConverter.ToChar(new byte[] { firstByte, secondByte }, 0);
                   
                    if (readChar == secondEol && expectNextIsEol)
                    {
                        //EOL found!!!!!!!
                        result.Add(counter);
                    }
                    else
                    {
                        expectNextIsEol = false;
                    }

                    if (firstEol == readChar)
                    {
                        expectNextIsEol = true;
                    }

                    firstRead = false;
                }
                else
                {
                    firstByte = (byte)readByte;
                    firstRead = true;
                }
                counter++;
            }

            return RecordLocation.FromPositions(result);
        }

        public List<Student> FindStudents(String searchPattern)
        {
            CheckFileStatus();

            SeekToStart();

            List<Student> result = new List<Student>();

            using (StreamReader reader = new StreamReader(_fileStream,Encoding.Unicode,true,1024,true))
            {
                String line;
                while((line = reader.ReadLine()) != null)
                {
                    if (line[0] == '\0')
                        continue;

                    Student std = new Student(line, _fileDelimeter, _dateFormat);

                    if (std.FitSearchPattern(searchPattern))
                    {
                        result.Add(std);
                    }
                }
            }
            return result;
        }

        public void Create(Student newStudent)
        {
            CheckFileStatus();

            _fileStream.Seek(0, SeekOrigin.End);

            using (StreamWriter writer = new StreamWriter(_fileStream,Encoding.Unicode,1024,true))
            {

                //Serialize new instance
                String serializedInstance = newStudent.SerializeToString(_fileDelimeter, _dateFormat);

                writer.WriteLine(serializedInstance);
            }
        }

        public void Update(Student existingStudent)
        {
            Delete(existingStudent);
            Create(existingStudent);
        }

        public void Delete(Student studentToDelete)
        {
            RecordLocation location = GetRecordLocationByKey(studentToDelete.Id);

            if (location != null)
            {
                _fileStream.Seek(location.StartPos, SeekOrigin.Begin);
                byte[] bytes = BitConverter.GetBytes('\0');
                _fileStream.Write(bytes, 0, bytes.Length);
                _fileStream.Flush();
            }
        }
    }
}
